#include <UDTClient.h>
#include "cc.h"
#include <iostream>
#include <sstream>
#include <Poco/Net/DNS.h>
#include <Poco/Net/SocketAddress.h>

using namespace std;
UDTClient::UDTClient(Poco::UInt64 aSession , const Poco::Net::SocketAddress& aUdtServerAddr)
:TryConnect(this , &UDTClient::TryConnectImp )
,iSessionID(aSession)
,iUdtServerAddr(aUdtServerAddr)

{
}
UDTClient::UDTClient(Poco::UInt64 aSession , const Poco::Net::SocketAddress& aUdtServerAddr , const Poco::Net::SocketAddress& aUDPServerAddr)
:TryConnect(this , &UDTClient::TryConnectImp )
,iSessionID(aSession)
,iUdtServerAddr(aUdtServerAddr)
,iUDPServerAddr(aUDPServerAddr)

{

}
/*
Function: ConnectToServer
Input:
Output:
Purpose: auto search local port to bind and connect to server.
*/
#include <Poco/NumberFormatter.h>
bool UDTClient::ConnectToServer(UDTSOCKET aSocket) // bind only
{



	for (Poco::UInt16 aPort = 9001; aPort < 10000; aPort ++)
	{


		Poco::Net::SocketAddress aAddr("0.0.0.0" , aPort);


		if (UDT::ERROR != UDT::bind( aSocket , aAddr.addr() , aAddr.length() /*local->ai_addr , local->ai_addrlen */) )
		{

#ifdef WIN32
			UDT::setsockopt(aSocket, 0, UDT_MSS, new int(1052), sizeof(int));
#endif

			// connect to the server, implict bind
			if (UDT::ERROR == UDT::connect(aSocket, iUdtServerAddr.addr() , iUdtServerAddr.length() /*peer->ai_addr, peer->ai_addrlen*/))
			{
				cout << "connect: " << UDT::getlasterror().getErrorMessage() << endl;
				return false;
			}

			return true;
		}

	}
	return false;
}

void UDTClient::GetPeerAddr(UDTSOCKET client, Poco::Net::SocketAddress& aPeerAddress)
{
	UDT::send(client , (char*)(&iSessionID) , sizeof(iSessionID) , 0 );

	char data[1024];
	int aRecLength = UDT::recv(client , data, 1024 , 0);
	std::string aRev(data , aRecLength);
	//cout<<aRev <<endl;
	stringstream aStrStream;
	aStrStream.str(aRev);
	std::string aMyAddr;
	std::string aPeerAddr;
	aStrStream>>aMyAddr>>aPeerAddr;
	aPeerAddress = Poco::Net::SocketAddress(aPeerAddr);

}

void UDTClient::GetPeerAddress(UDTSOCKET client, std::string& aHost , std::string& aPort)
{

	UDT::send(client , (char*)(&iSessionID) , sizeof(iSessionID) , 0 );

	char data[1024];
	int aRecLength = UDT::recv(client , data, 1024 , 0);
	std::string aRev(data , aRecLength);
	cout<<aRev <<endl;
	stringstream aStrStream;
	aStrStream.str(aRev);

	std::string aMyAddr;
	std::string aPeerAddr;

	std::string aMyHost;
	std::string aMyPort;

	aStrStream>>aMyHost>>aMyPort>> aHost >> aPort;
	//aPeerAddress = Poco::Net::SocketAddress(aPeerAddr);

}

void UDTClient::GetPeerAddress(UDTSOCKET client, std::string& aPeerHostPort)
{

	// SEnd Session ID
	UDT::send(client , (char*)(&iSessionID) , sizeof(iSessionID) , 0 );
	// Send local address
	sockaddr_storage clientaddr;
	int addrlen = sizeof(clientaddr);
	char clienthost[NI_MAXHOST];
	char clientservice[NI_MAXSERV];


	if (UDT::ERROR == UDT::getsockname(client , (sockaddr*)&clientaddr , &addrlen ) )
	{
		{
			cout << "getsockname: " << UDT::getlasterror().getErrorMessage() << endl;
			return ;
		}

	}

	getnameinfo((sockaddr *)&clientaddr, addrlen, clienthost, sizeof(clienthost), clientservice, sizeof(clientservice), NI_NUMERICHOST|NI_NUMERICSERV);

	std::string aLocalAddressList = clientservice;

	Poco::Net::HostEntry aHost = Poco::Net::DNS::thisHost();
	Poco::Net::HostEntry::AddressList aAddrList = aHost.addresses();

	aLocalAddressList += " "+ Poco::NumberFormatter::format( aAddrList.size() );

	for (Poco::Net::HostEntry::AddressList::iterator aIt = aAddrList.begin() ; aIt != aAddrList.end() ; aIt++)
	{
		//cout<<aIt->toString()<<endl;
		aLocalAddressList = aLocalAddressList+ " " + aIt->toString();
	}
	SendData(client , aLocalAddressList );
	//Poco::UInt16 aSize = aLocalAddressList.length();
	//UDT::send(client , (const char* )&aSize , sizeof (aSize) );
	/////////////

	//char data[1024];
	//int aRecLength = UDT::recv(client , data, 1024 , 0);
	//std::string aRev(data , aRecLength);

	std::string aRev;
	ReceiveData(client , aRev);

	cout<<aRev <<endl;
	stringstream aStrStream;
	aStrStream.str(aRev);

	std::string aMyAddr;

	aStrStream>>aMyAddr>>aPeerHostPort;
	//aPeerAddress = Poco::Net::SocketAddress(aPeerAddr);


}

bool UDTClient::GetPeerAddress(UDTSOCKET client, std::list<Poco::Net::SocketAddress>& aPeerAddrList )
{
	// SEnd Session ID
	UDT::send(client , (char*)(&iSessionID) , sizeof(iSessionID) , 0 );
	// Send local address
	sockaddr_storage clientaddr;
	int addrlen = sizeof(clientaddr);
	char clienthost[NI_MAXHOST];
	char clientservice[NI_MAXSERV];


	if (UDT::ERROR == UDT::getsockname(client , (sockaddr*)&clientaddr , &addrlen ) )
	{
		{
			cout << "getsockname: " << UDT::getlasterror().getErrorMessage() << endl;
			return false;
		}

	}

	getnameinfo((sockaddr *)&clientaddr, addrlen, clienthost, sizeof(clienthost), clientservice, sizeof(clientservice), NI_NUMERICHOST|NI_NUMERICSERV);

	std::string aLocalAddressList = clientservice;

	Poco::Net::HostEntry aHost = Poco::Net::DNS::thisHost();
	Poco::Net::HostEntry::AddressList aAddrList = aHost.addresses();

	aLocalAddressList += " "+ Poco::NumberFormatter::format( aAddrList.size() );

	for (Poco::Net::HostEntry::AddressList::iterator aIt = aAddrList.begin() ; aIt != aAddrList.end() ; aIt++)
	{
		aLocalAddressList = aLocalAddressList+ " " + aIt->toString();
	}
	if (!SendData(client , aLocalAddressList ) )
		return false;

	std::string aRev;
	if (!ReceiveData(client , aRev) )
		return false;

	cout<<aRev <<endl;
	stringstream aStrStream;
	aStrStream.str(aRev);

	std::string aMyAddr;
	std::string aPeerHostPort;


	aStrStream>>aMyAddr>>aPeerHostPort;
	aPeerAddrList.push_back( Poco::Net::SocketAddress(aPeerHostPort) );

	Poco::UInt16 aLocalPeerPort;
	aStrStream>>aLocalPeerPort;
	cout << "Peer Local" << aLocalPeerPort<<endl;
	int aNumLocalPeerIP;
	aStrStream>>aNumLocalPeerIP;
	for (int aIndex = 0; aIndex < aNumLocalPeerIP; aIndex++)
	{
		std::string aPeerLocalIp;
		aStrStream>>aPeerLocalIp;
		aPeerAddrList.push_back( Poco::Net::SocketAddress(aPeerLocalIp , aLocalPeerPort ) );
	}
	return true;

}

bool UDTClient::ConnectWithPeerImp()
{

	if (UDPHolePuching())
		return true;
	cout<<"UDPHolePuching failed"<<endl;

	UDTSOCKET client = UDT::socket(AF_INET, SOCK_STREAM, 0); // neu can ho tro ipv6 thi lam sau


	if (!ConnectToServer(client) )
		return false;

	UDTSOCKET client2 = UDT::socket(AF_INET, SOCK_STREAM, 0);
	UDT::setsockopt(client2, 0, UDT_RENDEZVOUS, new bool(true), sizeof(bool));

	std::string aPeerHostPort;

	//GetPeerAddress(client , aPeerHostPort);
	std::list<Poco::Net::SocketAddress> aPeerAddrList;
	if (!GetPeerAddress(client , aPeerAddrList) )
		return false;

	//CreateConnectionToFriend(client , client2 , aPeerHostPort);
	if (!CreateConnectionToFriend(client , client2 , aPeerAddrList) )
		return false;

	//iPeerSocket = client2;
	return true;

}

//bool UDTClient::CreateConnectionToFriend(UDTSOCKET aConnectedToServerSocket, UDTSOCKET aFuturePeerSocket, const std::string& aPeerHostPort)
//{
//	sockaddr_storage clientaddr;
//	int addrlen = sizeof(clientaddr);
//	char clienthost[NI_MAXHOST];
//	char clientservice[NI_MAXSERV];
//
//
//	if (UDT::ERROR == UDT::getsockname(aConnectedToServerSocket , (sockaddr*)&clientaddr , &addrlen ) )
//	{
//		{
//			cout << "getsockname: " << UDT::getlasterror().getErrorMessage() << endl;
//			return false;
//		}
//
//	}
//
//	getnameinfo((sockaddr *)&clientaddr, addrlen, clienthost, sizeof(clienthost), clientservice, sizeof(clientservice), NI_NUMERICHOST|NI_NUMERICSERV);
//
//	Poco::Net::SocketAddress aNewLocalAddress("0.0.0.0" , clientservice);
//
//	if (UDT::ERROR == UDT::bind(aFuturePeerSocket, aNewLocalAddress.addr() , aNewLocalAddress.length()  /* local->ai_addr, local->ai_addrlen*/))
//	{
//		cout << "bind: " << UDT::getlasterror().getErrorMessage() << endl;
//		return false;
//	}
//
//
//	Poco::Net::SocketAddress aPeerSockAddress(aPeerHostPort);
//
//	if (UDT::ERROR == UDT::connect(aFuturePeerSocket, aPeerSockAddress.addr() , aPeerSockAddress.length() /*peer->ai_addr, peer->ai_addrlen*/))
//	{
//		cout <<"error connect 2"<<UDT::getlasterror().getErrorMessage() << endl;;
//		Poco::Net::SocketAddress aTryAddr("192.168.1.5" , aPeerSockAddress.port() );
//		if (UDT::ERROR == UDT::connect(aFuturePeerSocket, aTryAddr.addr() , aTryAddr.length() ) )
//			cout <<"error connect retry: "<<UDT::getlasterror().getErrorMessage() << endl;;
//		return false;
//	}
//	return true;
//
//
//}
//
bool UDTClient::CreateConnectionToFriend(UDTSOCKET aConnectedToServerSocket, UDTSOCKET aFuturePeerSocket, const std::list<Poco::Net::SocketAddress>& aPeerAddrList)
{
	sockaddr_storage clientaddr;
	int addrlen = sizeof(clientaddr);
	char clienthost[NI_MAXHOST];
	char clientservice[NI_MAXSERV];


	if (UDT::ERROR == UDT::getsockname(aConnectedToServerSocket , (sockaddr*)&clientaddr , &addrlen ) )
	{
		{
			cout << "getsockname: " << UDT::getlasterror().getErrorMessage() << endl;
			return false;
		}

	}

	getnameinfo((sockaddr *)&clientaddr, addrlen, clienthost, sizeof(clienthost), clientservice, sizeof(clientservice), NI_NUMERICHOST|NI_NUMERICSERV);

	Poco::Net::SocketAddress aNewLocalAddress("127.0.0.1" , clientservice);
	cout << "My Local Port: "<<clientservice <<endl;

	if (UDT::ERROR == UDT::bind(aFuturePeerSocket, aNewLocalAddress.addr() , aNewLocalAddress.length()  /* local->ai_addr, local->ai_addrlen*/))
	{
		cout << "bind: " << UDT::getlasterror().getErrorMessage() << endl;
		return false;
	}


	std::list<Poco::Net::SocketAddress>::const_iterator aPeerIt = aPeerAddrList.begin();
	std::list< Poco::ActiveResult<TConnectResult> > aResultList;

	for (; aPeerIt != aPeerAddrList.end() ; aPeerIt++)
	{
		aResultList.push_back (TryConnect( TConnectParam(aFuturePeerSocket , *aPeerIt ) ) );
		//cout<<"connecting to "<<aPeerIt->toString() <<endl;

		//if (UDT::ERROR == UDT::connect(aFuturePeerSocket, aPeerIt->addr() , aPeerIt->length() ) )
		//	cout <<"error connect 2"<<UDT::getlasterror().getErrorMessage() << endl;
		//else 
		//	{
		//		iPeerSocket = aFuturePeerSocket;
		//		return true;
		//}
	}

	std::list< Poco::ActiveResult<TConnectResult> > ::iterator aResultIt;
	for (aResultIt = aResultList.begin() ; aResultIt != aResultList.end() ; aResultIt++)
	{
		aResultIt->wait();
		if (aResultIt->data().get<0>() )
		{
			iPeerSocket = aResultIt->data().get<1>();
			return true;
		}
	}
	// bad case, server is proxy now
	iPeerSocket = aConnectedToServerSocket;
	return true;


}


UDTClient::TConnectResult UDTClient::TryConnectImp(const TConnectParam& aConnectionParam)
{

	sockaddr_storage clientaddr;
	int addrlen = sizeof(clientaddr);
	char clienthost[NI_MAXHOST];
	char clientservice[NI_MAXSERV];

	Poco::Net::SocketAddress aPeer =  aConnectionParam.get<1>();
	cout<<"connecting to "<<aPeer.toString() <<endl;

	UDTSOCKET aConnectedToServerSocket = aConnectionParam.get<0>();

	//////////
	if (UDT::ERROR == UDT::getsockname(aConnectedToServerSocket , (sockaddr*)&clientaddr , &addrlen ) )
	{
		{
			cout << "getsockname: " << UDT::getlasterror().getErrorMessage() << endl;
			return TConnectResult(false , 0);
		}

	}

	getnameinfo((sockaddr *)&clientaddr, addrlen, clienthost, sizeof(clienthost), clientservice, sizeof(clientservice), NI_NUMERICHOST|NI_NUMERICSERV);

	Poco::Net::SocketAddress aNewLocalAddress("127.0.0.1" , clientservice);
	cout << "My Local Port: "<<clientservice <<endl;

	UDTSOCKET aFuturePeerSocket = UDT::socket(AF_INET, SOCK_STREAM, 0);
	UDT::setsockopt(aFuturePeerSocket, 0, UDT_RENDEZVOUS, new bool(true), sizeof(bool));


	if (UDT::ERROR == UDT::bind(aFuturePeerSocket, aNewLocalAddress.addr() , aNewLocalAddress.length()  /* local->ai_addr, local->ai_addrlen*/))
	{
		cout << "bind: " << UDT::getlasterror().getErrorMessage() << endl;
		UDT::close(aFuturePeerSocket);
		return  TConnectResult(false , 0);
	}
	///////


	if (UDT::ERROR == UDT::connect( aFuturePeerSocket , aConnectionParam.get<1>().addr() , aConnectionParam.get<1>().length() ))
	{
		cout <<"error connect 2"<<UDT::getlasterror().getErrorMessage() << endl;
		UDT::close(aFuturePeerSocket);
		return TConnectResult(false , 0);
	}
	cout << "Connect successful with "<< aPeer.toString()<<endl;
	return TConnectResult(true , aFuturePeerSocket);
}


bool UDTClient::SendData(UDTSOCKET aSocket,const char* aData, int size)
{
	int ssize = 0;
	int ss;
	while (ssize < size)
	{
		if (UDT::ERROR == (ss = UDT::send(aSocket, aData + ssize, size - ssize, 0)))
		{
			cout << "send:" << UDT::getlasterror().getErrorMessage() << endl;
			return false;
			break;
		}

		ssize += ss;
	} 

	return true;
}

bool UDTClient::SendData(UDTSOCKET aSocket , const std::string& aData)
{
	Poco::UInt16 aLength = aData.length();
	if (!SendData(aSocket , (char*)&aLength , sizeof(aLength) ) )
		return false;

	return SendData(aSocket , aData.data() , aData.length() );
}

bool UDTClient::ReceiveData(UDTSOCKET aSocket , char* aData, int size)
{
	int rsize = 0;
	int rs;
	while (rsize < size)
	{
		if (UDT::ERROR == (rs = UDT::recv(aSocket, aData + rsize, size - rsize, 0)))
		{
			cout << "recv:" << UDT::getlasterror().getErrorMessage() << endl;
			return false;

		}

		rsize += rs;
	}
	return true;
}

bool UDTClient::ReceiveData(UDTSOCKET aSocket , std::string& aData)
{
	Poco::UInt16 aLength;
	if (!ReceiveData(aSocket , (char*)&aLength , sizeof (aLength) ) )
		return false;
	aData.resize(aLength );
	return ReceiveData(aSocket , const_cast<char*>(aData.data() ) , aLength );

}


bool UDTClient::UDPHolePuching()
{
	int aUDPSocket = ::socket(AF_INET , SOCK_DGRAM , 0);
	::sendto(aUDPSocket , (char*)(&iSessionID) , sizeof(iSessionID) , 0, iUDPServerAddr.addr() ,iUDPServerAddr.length() );

	char aData[1024];
	sockaddr_storage aAddr;
	int aAdLen = sizeof(aAddr);
	int aSb = ::recvfrom(aUDPSocket , aData , sizeof (aData ), 0 , (sockaddr*)&aAddr , &aAdLen );


	//iUdpSocket.bind( Poco::Net::SocketAddress("0.0.0.0:10000") );
	//iUdpSocket.connect( iUDPServerAddr );
	//iUdpSocket.sendBytes((char*)(&iSessionID) , sizeof(iSessionID) );
	////iUdpSocket.sendTo((char*)(&iSessionID) , sizeof(iSessionID) , iUDPServerAddr);
	////int aSocket = ::socket(AF_INET , SOCK_DGRAM , 0);

	//// 
	//cout << "local addr "<<iUdpSocket.address().toString()<<endl;
	////
	//char aAddrInfoBytes[1024];
	//int aSb = iUdpSocket.receiveBytes (aAddrInfoBytes , sizeof (aAddrInfoBytes) );
	////Poco::Net::SocketAddress aNewAddr;
	////int aSb = iUdpSocket.receiveFrom(aAddrInfoBytes , sizeof (aAddrInfoBytes) , aNewAddr );
	//aAddrInfoBytes[aSb] = 0;

	//Poco::Net::SocketAddress aSocketAddress(aAddrInfoBytes); // peer addr
	//cout<<"UDPHole Peer Addr "<<aAddrInfoBytes<<endl;

	////iUdpSocket.sendTo("xinchao" , 6 , aSocketAddress);

	//////////////////////////////////////////////////////////////////////////////
	////Poco::Net::SocketAddress aNewAddr2;
	////int aSb2 = iUdpSocket.receiveFrom(aAddrInfoBytes , sizeof (aAddrInfoBytes) , aNewAddr2 );
	////aAddrInfoBytes[aSb2] = 0;
	////cout<<"UDPHole Peer Data"<<aAddrInfoBytes<< " " <<aNewAddr2.toString() << endl;

	////////////////////////////////////////////////////////////////////////////


	UDTSOCKET aSock = UDT::socket(AF_INET, SOCK_STREAM, 0);
	bool rendezvous = true;
	UDT::setsockopt(aSock, 0, UDT_RENDEZVOUS, &rendezvous, sizeof(bool));
	if (UDT::ERROR == UDT::bind(aSock , iUdpSocket.impl()->sockfd() ) )
	{

		cout<<"UDPHolePuching ErrorBind " <<endl;
		return false;
	};


#ifdef WIN32
	UDT::setsockopt(aSock, 0, UDT_MSS, new int(1052), sizeof(int));
#endif

	sockaddr_storage aAddrStorage;
	int addrlen = sizeof(aAddrStorage);

	UDT::getsockname(aSock , (sockaddr*)&aAddrStorage , &addrlen );

	cout<<"sockname udt: "<<Poco::Net::SocketAddress( (sockaddr*)&aAddrStorage , addrlen ).toString()<<endl;

	for (int i = 0; i< 5 ; i++)
	if ( UDT::ERROR == UDT::connect(aSock , aSocketAddress.addr() , aSocketAddress.length() ) )
	{
		cout<<"Error UDPHolePuching Connect" <<endl;
		cout<< UDT::getlasterror().getErrorMessage() <<endl;
		
	}
	else break;


	UDT::getsockname(aSock , (sockaddr*)&aAddrStorage , &addrlen );

	cout<<"sockname udt: "<<Poco::Net::SocketAddress( (sockaddr*)&aAddrStorage , addrlen ).toString()<<endl;


	iPeerSocket = aSock;
	return true;
}